﻿
namespace EasyNet.FastDFS
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Configuration;
    using System.IO;

    public class TrackerClient
    {
        private static IDictionary<string, TrackerClient> instances = new Dictionary<string, TrackerClient>();
        private const char SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR = '/';

        /// <summary>
        /// 初始化Tracker配置
        /// </summary>
        /// <param name="name">名称</param>
        /// <param name="servers">服务地址数组</param>
        public static void Setup(string name, string[] servers)
        {
            if (instances.ContainsKey(name))
            {
                throw new ConfigurationErrorsException("Trying to configure TrackerClient instance \"" + name + "\" twice.");
            }
            instances[name] = new TrackerClient(name, servers);
        }

        private static TrackerClient defaultInstance = null;

        /// <summary>
        /// 获取Tracker默认实例
        /// </summary>
        /// <returns></returns>
        public static TrackerClient GetInstance()
        {
            return defaultInstance ?? (defaultInstance = GetInstance("Default"));
        }

        /// <summary>
        /// 获取指定名称的TrackerClient实例
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public static TrackerClient GetInstance(string name)
        {
            TrackerClient trackerClient;

            if (instances.TryGetValue(name, out trackerClient))
            {
                return trackerClient;
            }

            var config = ConfigurationManager.GetSection("FDFSTrackers") as NameValueCollection;

            if (config != null && !string.IsNullOrEmpty(config.Get(name)))
            {
                Setup(name, config.Get(name).Split(new char[] { ',', ' ' }));

                return GetInstance(name);
            }

            throw new ConfigurationErrorsException("Unable to find TrackerClient instance \"" + name + "\".");

        }

        private readonly ServerPool serverPool;

        public int SendReceiveTimeout
        {
            get
            {
                return serverPool.SendReceiveTimeout;
            }
            set
            {
                serverPool.SendReceiveTimeout = value;
            }
        }

        public int ConnectTimeout
        {
            get
            {
                return serverPool.ConnectTimeout;
            }
            set
            {
                serverPool.ConnectTimeout = value;
            }
        }

        public uint MinPoolSize
        {
            get
            {
                return serverPool.MinPoolSize;
            }
            set
            {
                if (value > MaxPoolSize)
                {
                    throw new ConfigurationErrorsException("MinPoolSize (" + value + ") may not be larger than the MaxPoolSize (" + MaxPoolSize + ").");
                }

                serverPool.MinPoolSize = value;
            }
        }

        public uint MaxPoolSize
        {
            get
            {
                return serverPool.MaxPoolSize;
            }
            set
            {
                if (value < MinPoolSize)
                {
                    throw new ConfigurationErrorsException("MaxPoolSize (" + value + ") may not be smaller than the MinPoolSize (" + MinPoolSize + ").");
                }

                serverPool.MaxPoolSize = value;
            }
        }

        public TimeSpan SocketRecycleAge
        {
            get
            {
                return serverPool.SocketRecycleAge;
            }
            set
            {
                serverPool.SocketRecycleAge = value;
            }
        }

        private TrackerClient(string name, string[] hosts)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ConfigurationErrorsException("Name of TrackerClient instance cannot be empty.");
            }
            if (hosts == null || hosts.Length == 0)
            {
                throw new ConfigurationErrorsException("Cannot configure TrackerClient with empty list of hosts.");
            }

            serverPool = new ServerPool(hosts);
        }

        #region 获取上传Storage

        /// <summary>
        /// 获取Storage
        /// </summary>
        /// <returns>Storage信息</returns>
        public StorageServer GetStoreStorage()
        {
            return GetStoreStorage(null);
        }

        /// <summary>
        /// 获取上传Storage
        /// </summary>
        /// <param name="groupName">组名称</param>
        /// <returns>Storage信息</returns>
        public StorageServer GetStoreStorage(string groupName)
        {
            return serverPool.Execute<StorageServer>(serverPool.GetRandmonSocketPool(), null, delegate(PooledSocket socket)
            {
                byte[] header;
                string ip_addr;
                int port;
                byte cmd;
                int out_len;
                byte store_path;

                if (string.IsNullOrEmpty(groupName))
                {
                    cmd = Protocol.TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ONE;
                    out_len = 0;
                }
                else
                {
                    cmd = Protocol.TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITH_GROUP_ONE;
                    out_len = Protocol.FDFS_GROUP_NAME_MAX_LEN;
                }

                header = Protocol.PackHeader(cmd, out_len, (byte)0);
                socket.Write(header);

                if (!string.IsNullOrEmpty(groupName))
                {
                    byte[] bGroupName;
                    byte[] bs;
                    int group_len;

                    bs = Protocol.Encoding.GetBytes(groupName);
                    bGroupName = new byte[Protocol.FDFS_GROUP_NAME_MAX_LEN];

                    if (bs.Length <= Protocol.FDFS_GROUP_NAME_MAX_LEN)
                    {
                        group_len = bs.Length;
                    }
                    else
                    {
                        group_len = Protocol.FDFS_GROUP_NAME_MAX_LEN;
                    }

                    for (var i = 0; i < bGroupName.Length; i++)
                    {
                        bGroupName[i] = (byte)0;
                    }

                    Array.Copy(bs, 0, bGroupName, 0, group_len);
                    socket.Write(bGroupName);
                }

                var pkgInfo = Protocol.PackPackage(socket.GetStream(),
                                             Protocol.TRACKER_PROTO_CMD_RESP,
                                             Protocol.TRACKER_QUERY_STORAGE_STORE_BODY_LEN);
                if (pkgInfo.ErrorCode != 0)
                {
                    return null;
                }

                ip_addr = Protocol.Encoding.GetString(pkgInfo.Body, Protocol.FDFS_GROUP_NAME_MAX_LEN, Protocol.FDFS_IPADDR_SIZE - 1).Trim('\0');

                port = (int)Protocol.ConvertBuffToLong(pkgInfo.Body, Protocol.FDFS_GROUP_NAME_MAX_LEN
                                + Protocol.FDFS_IPADDR_SIZE - 1);
                store_path = pkgInfo.Body[Protocol.TRACKER_QUERY_STORAGE_STORE_BODY_LEN - 1];

                return new StorageServer() { IP = ip_addr, Port = port, StorePath = store_path };
            });
        }

        /// <summary>
        /// 获取一组上传Storage
        /// </summary>
        /// <param name="groupName">组名称</param>
        /// <returns>一组Storage信息</returns>
        public StorageServer[] GetStoreStorages(string groupName)
        {
            return serverPool.Execute<StorageServer[]>(serverPool.GetRandmonSocketPool(), null, delegate(PooledSocket socket)
            {
                byte[] header;
                string ip_addr;
                int port;
                byte cmd;
                int out_len;


                if (string.IsNullOrEmpty(groupName))
                {
                    cmd = Protocol.TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITHOUT_GROUP_ALL;
                    out_len = 0;
                }
                else
                {
                    cmd = Protocol.TRACKER_PROTO_CMD_SERVICE_QUERY_STORE_WITH_GROUP_ALL;
                    out_len = Protocol.FDFS_GROUP_NAME_MAX_LEN;
                }
                header = Protocol.PackHeader(cmd, out_len, (byte)0);
                socket.Write(header);

                if (!string.IsNullOrEmpty(groupName))
                {
                    byte[] bGroupName;
                    byte[] bs;
                    int group_len;

                    bs = Protocol.Encoding.GetBytes(groupName);
                    bGroupName = new byte[Protocol.FDFS_GROUP_NAME_MAX_LEN];

                    if (bs.Length <= Protocol.FDFS_GROUP_NAME_MAX_LEN)
                    {
                        group_len = bs.Length;
                    }
                    else
                    {
                        group_len = Protocol.FDFS_GROUP_NAME_MAX_LEN;
                    }

                    for (var i = 0; i < bGroupName.Length; i++)
                    {
                        bGroupName[i] = (byte)0;
                    }
                    Array.Copy(bs, 0, bGroupName, 0, group_len);
                    socket.Write(bGroupName);
                }

                var pkgInfo = Protocol.PackPackage(socket.GetStream(),
                                             Protocol.TRACKER_PROTO_CMD_RESP, -1);

                if (pkgInfo.ErrorCode != 0)
                {
                    return null;
                }

                if (pkgInfo.Body.Length < Protocol.TRACKER_QUERY_STORAGE_STORE_BODY_LEN)
                {
                    return null;
                }

                int ipPortLen = pkgInfo.Body.Length - (Protocol.FDFS_GROUP_NAME_MAX_LEN + 1);
                int recordLength = Protocol.FDFS_IPADDR_SIZE - 1 + Protocol.FDFS_PROTO_PKG_LEN_SIZE;

                if (ipPortLen % recordLength != 0)
                {
                    return null;
                }

                int serverCount = ipPortLen / recordLength;
                if (serverCount > 16)
                {
                    return null;
                }

                var results = new StorageServer[serverCount];
                byte store_path = pkgInfo.Body[pkgInfo.Body.Length - 1];
                int offset = Protocol.FDFS_GROUP_NAME_MAX_LEN;

                for (int i = 0; i < serverCount; i++)
                {
                    ip_addr = Protocol.Encoding.GetString(pkgInfo.Body, offset, Protocol.FDFS_IPADDR_SIZE - 1).Trim('\0');
                    offset += Protocol.FDFS_IPADDR_SIZE - 1;

                    port = (int)Protocol.ConvertBuffToLong(pkgInfo.Body, offset);
                    offset += Protocol.FDFS_PROTO_PKG_LEN_SIZE;

                    results[i] = new StorageServer() {IP = ip_addr, Port = port, StorePath = store_path };
                }
                return results;
            });
        }

        #endregion

        #region 获取下载、上传的Storage

        /// <summary>
        /// 获取下载的Storage
        /// </summary>
        /// <param name="groupName">组名称</param>
        /// <param name="filename">文件名称</param>
        /// <returns>Storage</returns>
        public StorageServer GetFetchStorage(string groupName, string filename)
        {
            var servers = GetStorages(Protocol.TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ONE, groupName, filename);

            if (servers == null)
            {
                return null;
            }
            else
            {
                return new StorageServer() {IP = servers[0].IP, Port = servers[0].Port };
            }
        }

        /// <summary>
        /// 获取更新的Storage
        /// </summary>
        /// <param name="groupName">组名称</param>
        /// <param name="filename">文件名称</param>
        /// <returns>Storage</returns>
        public StorageServer GetUpdateStorage(string groupName, string filename)
        {
            var servers = GetStorages(Protocol.TRACKER_PROTO_CMD_SERVICE_QUERY_UPDATE, groupName, filename);

            if (servers == null)
            {
                return null;
            }
            else
            {
                return new StorageServer { IP = servers[0].IP, Port = servers[0].Port };
            }
        }

        /// <summary>
        /// 获取所有下载的Storage
        /// </summary>
        /// <param name="groupName">组名称</param>
        /// <param name="filename">文件名称</param>
        /// <returns>一组Storage</returns>
        public ServerInfo[] GetFetchStorages(string groupName, string filename)
        {
            return GetStorages(Protocol.TRACKER_PROTO_CMD_SERVICE_QUERY_FETCH_ALL, groupName, filename);
        }

        /// <summary>
        /// 根据文件Id获取下载的Storage
        /// </summary>
        /// <param name="fileId">文件Id</param>
        /// <returns>Storage</returns>
        public StorageServer GetFetchStorage(string fileId)
        {
            var result = fileId.Split(SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR);

            if (result == null || result.Length < 2)
            {
                return null;
            }

            return GetFetchStorage(result[0], result[1]);
        }

        /// <summary>
        /// 根据文件Id获取下载的Storage
        /// </summary>
        /// <param name="fileId">文件Id</param>
        /// <returns>一组Storage</returns>
        public ServerInfo[] GetFetchStorages(string fileId)
        {
            var result = fileId.Split(SPLIT_GROUP_NAME_AND_FILENAME_SEPERATOR);

            if (result == null || result.Length < 2)
            {
                return null;
            }

            return GetFetchStorages(result[0], result[1]);
        }

        /// <summary>
        /// 获取下载、上传的Storage
        /// </summary>
        /// <param name="cmd">命令</param>
        /// <param name="groupName">组名称</param>
        /// <param name="filename">文件名称</param>
        /// <returns>一组Storage信息</returns>
        protected StorageServer[] GetStorages(byte cmd, string groupName, string filename)
        {
            return serverPool.Execute<StorageServer[]>(serverPool.GetRandmonSocketPool(), null, delegate(PooledSocket socket)
            {
                byte[] header;
                byte[] bFileName;
                byte[] bGroupName;
                byte[] bs;
                int len;
                string ip_addr;
                int port;


                bs = Protocol.Encoding.GetBytes(groupName);
                bGroupName = new byte[Protocol.FDFS_GROUP_NAME_MAX_LEN];
                bFileName = Protocol.Encoding.GetBytes(filename);

                if (bs.Length <= Protocol.FDFS_GROUP_NAME_MAX_LEN)
                {
                    len = bs.Length;
                }
                else
                {
                    len = Protocol.FDFS_GROUP_NAME_MAX_LEN;
                }

                for (var i = 0; i < bGroupName.Length; i++)
                {
                    bGroupName[0] = (byte)0;
                }
                Array.Copy(bs, 0, bGroupName, 0, len);

                header = Protocol.PackHeader(cmd, Protocol.FDFS_GROUP_NAME_MAX_LEN + bFileName.Length, (byte)0);
                byte[] wholePkg = new byte[header.Length + bGroupName.Length + bFileName.Length];
                Array.Copy(header, 0, wholePkg, 0, header.Length);
                Array.Copy(bGroupName, 0, wholePkg, header.Length, bGroupName.Length);
                Array.Copy(bFileName, 0, wholePkg, header.Length + bGroupName.Length, bFileName.Length);
                socket.Write(wholePkg);

                var pkgInfo = Protocol.PackPackage(socket.GetStream(), Protocol.TRACKER_PROTO_CMD_RESP, -1);

                if (pkgInfo.ErrorCode != 0)
                {
                    return null;
                }

                if (pkgInfo.Body.Length < Protocol.TRACKER_QUERY_STORAGE_FETCH_BODY_LEN)
                {
                    throw new IOException("Invalid body length: " + pkgInfo.Body.Length);
                }

                if ((pkgInfo.Body.Length - Protocol.TRACKER_QUERY_STORAGE_FETCH_BODY_LEN) % (Protocol.FDFS_IPADDR_SIZE - 1) != 0)
                {
                    throw new IOException("Invalid body length: " + pkgInfo.Body.Length);
                }

                int server_count = 1 + (pkgInfo.Body.Length - Protocol.TRACKER_QUERY_STORAGE_FETCH_BODY_LEN) / (Protocol.FDFS_IPADDR_SIZE - 1);

                ip_addr = Protocol.Encoding.GetString(pkgInfo.Body, Protocol.FDFS_GROUP_NAME_MAX_LEN, Protocol.FDFS_IPADDR_SIZE - 1).Trim('\0');
                int offset = Protocol.FDFS_GROUP_NAME_MAX_LEN + Protocol.FDFS_IPADDR_SIZE - 1;

                port = (int)Protocol.ConvertBuffToLong(pkgInfo.Body, offset);
                offset += Protocol.FDFS_PROTO_PKG_LEN_SIZE;

                var servers = new StorageServer[server_count];
                servers[0] = new StorageServer() { IP = ip_addr, Port = port };
                for (int i = 1; i < server_count; i++)
                {
                    servers[i] = new StorageServer() {IP = Protocol.Encoding.GetString(pkgInfo.Body, offset, Protocol.FDFS_IPADDR_SIZE - 1).Trim('\0'), Port = port };
                    offset += Protocol.FDFS_IPADDR_SIZE - 1;
                }

                return servers;

            });
        }

        #endregion
    }
}
